// Copyright 2013 Petar Maymounkov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ripple

import (
	"fmt"
	"math/rand"
	"path"
	"sync"
	"time"

	"circuit/kit/rh"
)

func buildSlabHelp(name string, id uint64) string {
	return fmt.Sprintf(slabHelpFmt, path.Join("/", name), id)
}

const slabHelpFmt = `
  This is the directory for a slab ripple, named "%s", with QID ⟨Q%016x⟩.

  SYNOPSIS

  cd cmd     # Access command files for this slab.
  cat help   # Read this help screen.
    
  Making a new child subdirectory will create a subordinate slab ripple.
  Slab directories are garbage-collected.

` //

// Slab
type Slab struct {
	// immutable
	cmd   map[string]rh.FID
	name  string
	gtm   time.Time
	q     rh.Q
	clunk func()
	// mutable
	z struct {
		sync.Mutex
		atm    time.Time
		ripple map[string]RippleFID
	}
	//
	cmdFID     rh.FID
	helpFID    rh.FID
	cmdHelpFID rh.FID
}

type RippleFID interface {
	rh.FID
}

// NewSlab
func NewSlab(name string) (*Slab, rh.FID) {
	now := time.Now()
	s := &Slab{
		name: name,
		gtm:  now,
		q: rh.Q{
			ID:  uint64(rand.Int63()),
			Ver: 1,
		},
	}
	s.z.atm, s.z.ripple = now, make(map[string]RippleFID)
	//
	s.helpFID = NewHelp(buildSlabHelp(name, s.q.ID))
	s.cmdFID = NewCmd(s)
	s.cmdHelpFID = NewHelp(buildCmdHelp("cmd", uint64(rand.Int63())))
	//
	s.cmd = map[string]rh.FID{"cmd": s.cmdFID, "help": s.helpFID}
	//
	return s, s.Walk()
}

func (s *Slab) toc() (toc rh.DirChunk) {
	s.z.Lock()
	defer s.z.Unlock()
	// List attachment files
	for _, cmdFID := range s.cmd {
		dir, err := cmdFID.Stat()
		if err != nil {
			panic(err)
		}
		toc = append(toc, dir)
	}
	// List sub-slabs
	for _, rFID := range s.z.ripple {
		dir, err := rFID.Stat()
		if err != nil {
			panic(err)
		}
		toc = append(toc, dir)
	}
	return
}

func (s *Slab) Dump(fix string) {
	s.z.Lock()
	defer s.z.Unlock()
	for name, r := range s.z.ripple {
		fmt.Printf("%s %s\n", fix, name)
		slb, ok := r.(*slabFID)
		if !ok {
			continue
		}
		slb.s.Dump(fix + " " + name)
	}
}

func (s *Slab) ClunkWith(clunk func()) {
	s.clunk = clunk
}

func (s *Slab) scrub(name string, r RippleFID) {
	s.z.Lock()
	defer s.z.Unlock()
	//
	x, ok := s.z.ripple[name]
	if !ok {
		return
	}
	if r != x {
		return
	}
	delete(s.z.ripple, name)
}

func (s *Slab) Walk() rh.FID {
	return &slabFID{s: s}
}

// slabFID is the RH-facing side of a slab
type slabFID struct {
	rh.AnchoredFID
	rh.DontWriteFID
	//
	s *Slab
}

func (fid *slabFID) Clunk() {
	// Don't do anything.
	// When all pointers to this FID are gone, it will scrub itself from its parent automatically.
}

//
func (fid *slabFID) String() string {
	return "slab"
}

//
func (fid *slabFID) Q() rh.Q {
	return fid.s.q
}

// Open is nop.
func (fid *slabFID) Open(rh.Flag, rh.Intr) error {
	return nil
}

// Create allows creation of subdirectories, which would themselves be slabs
func (fid *slabFID) Create(name string, _ rh.Flag, mode rh.Mode, _ rh.Perm) (rh.FID, error) {
	defer func() {
		fid.s.Dump("")
	}()
	if mode.Attr != rh.ModeDir {
		return nil, rh.ErrPerm
	}
	s2, fid2 := fid.s.MakeSlab(name)
	if s2 == nil {
		return nil, rh.ErrExist
	}
	return fid2, nil
}

func (fid *slabFID) Stat() (*rh.Dir, error) {
	fid.s.z.Lock()
	defer fid.s.z.Unlock()
	//
	now := time.Now()
	return &rh.Dir{
		Q:      fid.s.q,
		Mode:   rh.Mode{Attr: rh.ModeDir},
		Perm:   0777,
		Atime:  now, //fid.s.z.atm,
		Mtime:  now, //fid.s.z.atm,
		Name:   fid.s.name,
		Length: int64(len(fid.s.cmd) + len(fid.s.z.ripple)),
		Uid:    uid(),
		Gid:    gid(),
	}, nil
}

//
func (fid *slabFID) Read(offset int64, count int, _ rh.Intr) (rh.Chunk, error) {
	var toc rh.DirChunk
	toc = fid.s.toc()
	if count == 0 {
		count = len(toc)
	}
	if int(offset) >= len(toc) {
		return nil, nil
	}
	toc = toc[offset:min(len(toc), int(offset)+count)]
	return toc, nil
}

func (fid *slabFID) Walk(wname []string) (rh.FID, error) {
	if len(wname) == 0 {
		return fid.s.Walk(), nil
	}
	if len(wname) > 1 {
		return nil, rh.ErrClash
	}
	// command?
	if cmdFID, ok := fid.s.cmd[wname[0]]; ok {
		return cmdFID.Walk(nil)
	}
	// ripple?
	if rFID, ok := fid.s.z.ripple[wname[0]]; ok {
		return rFID.Walk(nil)
	}
	return nil, rh.ErrNotExist
}
